//-----------------------------------------------------------------------
// <copyright file="ExtendedProperty.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
// </copyright>
// <author>Mike Fourie</author>
// <email>v-mifour</email>
// <date>2008-02-26</date>
// <summary>Gets, Sets or Deletes an extended property</summary>
//-----------------------------------------------------------------------
namespace Microsoft.Sdc.Tasks.Sql
{
    using System;
    using System.IO;
    using System.Data;
    using System.Data.SqlClient;
    using SdcSql = Microsoft.Sdc.Tasks.Configuration.SqlServer;
    using Microsoft.Build.Framework;

    /// <summary>
    /// Gets, Sets or Deletes an extended property on the database specified in the connection string.
    /// 
    /// </summary>
    /// <remarks>
    /// <code><![CDATA[<Sql.ExtendedProperty Action="action" DatabaseVersion="versionNumber" ServerName="serverName" DatabaseName="database" ConnectionString="oleDBConnectionString" CommandTimeout="timeout" ExtendedProperty="yourproperty"/>]]></code>
    /// <para>where:</para>
    /// <para><i>serverName</i></para>
    /// <para>The server hosting the database (ignored if a connection string is supplied)</para>
    /// <para><i>databaseName</i></para>
    /// <para>The database (ignored if a connection string is supplied)</para>
    /// <para>If you do not specify a server and database name you must specify a 
    /// <see cref="ConnectionString">connection string</see>.</para>
    /// <para><i>connectionString</i></para>
    /// <para>An OLE/DB connection string detailing the connection parameters required.</para>
    /// <para>If you do not specify a connection string you must specify a
    /// <see cref="ServerName">serverName</see> and <see cref="DatabaseName">databaseName</see></para>
    /// <para><i>timeout</i></para>
    /// <para>Maximum time allowed to perform the task (seconds)</para>
    /// <para><i>yourproperty</i></para>
    /// <para>The extended property to read</para>
    /// </remarks>
    /// <example>
    /// <code><![CDATA[
    /// <Project>
    ///     <Target Name="Test" >
    ///         <Sql.GetExtendedProperty
    ///             ServerName="(local)"
    ///             DatabaseName="testDB"
    ///             ExtendedProperty="YourExtendedProperty">
    ///             <Output TaskParameter="ExtendedPropertyValue" PropertyName="YourExtendedPropertyValue"/>
    ///         </Sql.GetExtendedProperty>
    ///         <Message Text="$(YourExtendedPropertyValue)"/>
    ///     </Target>
    /// </Project>
    /// ]]></code>    
    /// </example>
    public class ExtendedProperty : TaskBase
    {
        private string connectionString = String.Empty;
        private string extendedPropertyValue = String.Empty;
        private string extendedPropertyName = String.Empty;
        private string serverName = String.Empty;
        private string databaseName = String.Empty;
        private int commandTimeout = 60;
        private string action;

        /// <summary>
        /// Gets or sets the action.
        /// </summary>
        /// <value>The action.</value>
        [Required]
        public string Action
        {
            get { return this.action; }
            set { this.action = value; }
        }

        /// <summary>
        /// Set this property to define a database connection string.
        /// </summary>
        /// <value>The connection string.</value>
        public string ConnectionString
        {
            get
            {
                return (this.connectionString ?? String.Empty);
            }

            set
            {
                this.connectionString = value;
                return;
            }
        }

        /// <summary>
        /// Gets or sets the extended property.
        /// </summary>
        /// <value>The extended property.</value>
        [Required]
        public string ExtendedPropertyName
        {
            get { return this.extendedPropertyName; }
            set { this.extendedPropertyName = value; }
        }

        /// <summary>
        /// The version number to set
        /// </summary>
        /// <value>The extended property value.</value>
        [Output]
        public string ExtendedPropertyValue
        {
            get { return this.extendedPropertyValue; }
            set { this.extendedPropertyValue = value; }
        }

        /// <summary>
        /// The SQL Server name.
        /// </summary>
        /// <value>The name of the server.</value>
        public string ServerName
        {
            get
            {
                return (this.serverName ?? String.Empty);
            }

            set
            {
                this.serverName = value;
            }
        }

        /// <summary>
        /// Name of the database that the script will be run against.
        /// </summary>
        /// <value>The name of the database.</value>
        public string DatabaseName
        {
            get
            {
                return (this.databaseName ?? String.Empty);
            }

            set
            {
                this.databaseName = value;
            }
        }

        /// <summary>
        /// Gets or sets the wait time in seconds before terminating the
        /// attempt to execute a command and generating an error.
        /// </summary>
        /// <value>The command timeout.</value>
        /// <remarks>
        /// Has a default value of 60 seconds.
        /// </remarks>
        public int CommandTimeout
        {
            get { return this.commandTimeout; }
            set { this.commandTimeout = value; }
        }

        /// <summary>
        /// This is the main execute method that all tasks should implement
        /// </summary>
        /// <remarks>
        /// TaskException should be thrown in the event of errors
        /// </remarks>
        protected override void InternalExecute()
        {
            try
            {
                this.PerformAction();
            }
            catch (Exception ex)
            {
                Log.LogErrorFromException(ex, true);
            }          
        }

        private void PerformAction()
        {
            string commandText;
            string resource;

            System.Reflection.Assembly assem = this.GetType().Assembly;
            
            switch (this.Action)
            {
                case "Get":
                    resource = "Microsoft.Sdc.Tasks.Sql.GetExtendedProperty.sql";
                    break;
                case "Set":
                    resource = "Microsoft.Sdc.Tasks.Sql.SetExtendedProperty.sql";
                    break;
                case "Drop":
                    resource = "Microsoft.Sdc.Tasks.Sql.DropExtendedProperty.sql";
                    break;
                default:
                    Log.LogError(string.Format("Invalid Action Passed to ExtendedProperty: {0}. Actions supported are Get, Set and Drop", this.Action));
                    return;
            }

            using (Stream stream = assem.GetManifestResourceStream(resource))
            {
                using (StreamReader reader = new StreamReader(stream))
                {
                    commandText = reader.ReadToEnd();
                }
            }

            if (String.IsNullOrEmpty(this.ConnectionString))
            {
                this.ConnectionString = string.Format(
                    "Data Source={0};Integrated Security=SSPI;Initial Catalog={1}",
                    this.serverName,
                    this.DatabaseName);
            }

            using (SqlConnection sqlConnection = new SqlConnection(this.ConnectionString))
            {
                SqlCommand command = new SqlCommand(commandText, sqlConnection);
                command.CommandTimeout = this.CommandTimeout;

                SqlParameter extpValue = new SqlParameter("@ExtendedPropertyValue", SqlDbType.NVarChar, 4000);
                if (this.Action != "Delete")
                {
                    extpValue.Value = this.ExtendedPropertyValue;
                    extpValue.Direction = ParameterDirection.InputOutput;
                    command.Parameters.Add(extpValue);
                }

                SqlParameter extProperty = new SqlParameter("@ExtendedProperty", SqlDbType.NVarChar, 2000);
                extProperty.Value = this.ExtendedPropertyName;
                extProperty.Direction = ParameterDirection.InputOutput;

                command.Parameters.Add(extProperty);

                try
                {
                    sqlConnection.Open();
                    command.ExecuteNonQuery();
                    if (this.Action == "Get")
                    {
                        this.extendedPropertyValue = extpValue.Value.ToString();
                    }
                }
                catch (SqlException ex)
                {
                    Log.LogError(string.Format("SQL Error: {0}", ex));
                }
                catch (Exception ex)
                {
                    Log.LogError(string.Format("Unexpected Error: {0}", ex));
                }
                finally
                {
                    if (sqlConnection.State != ConnectionState.Closed)
                    {
                        sqlConnection.Close();
                    }
                }
            }
        }
    }
}